package com.gitee.wsl.math

import com.gitee.wsl.ext.base.ifValue
import com.gitee.wsl.math.random.nextGaussian
import kotlin.math.*
import kotlin.random.Random

object Math {

    const val SQRT_2 = 1.4142135623730951
    const val SQRT_PI = 1.7724538509055159
    const val SQRT_TAU = 2.5066282746310002
    const val PI_HALF = 1.5707963267948966
    const val PI = 3.141592653589793
    const val TAU = 6.283185307179586
    const val TWO_PI = TAU
    const val E = 2.718281828459045
    const val EULER_MASCHERONI = 0.5772156649015329

    const val EPSILON = 1e-10

    /**
     * The signum function.
     *
     * @return 1 if num > 0, -1 if num < 0, and 0 if num = 0
     */
    fun signum(num: Double): Int {
        return if (num < 0) {
            -1
        } else if (num == 0.0) {
            0
        } else {
            1
        }
    }

    /**
     * Performs Simpson's rule to integrate a function.
     *
     * @param function The function to integrate
     * @param min The lower bound of the integral
     * @param max The upper bound of the integral
     * @param n The number of intervals to use
     * @return The integral of the function
     */
    fun simpson( min: Double, max: Double, n: Int = 1000,function: (Double) -> Double): Double {
        val h = (max - min) / n
        var sum = function(min) + function(max)
        for (i in 1 until n) {
            val x = min + i * h
            sum += if (i % 2 == 0) 2 * function(x) else 4 * function(x)
        }
        return sum * h / 3.0
    }

    /**
     * Maps a value from a range of [min, max] to [destMin, destMax]
     */
    fun mapFloat(v: Float, min: Float, max: Float, destMin: Float, destMax: Float): Float =
        lerp(
            start = destMin,
            stop = destMax,
            norm(
                n = v.coerceIn(min, max),
                start = min,
                stop = max
            )
        )



    /**
     * Constrains a value between a minimum and maximum value.
     *
     * @method constrain
     * @param n   number to constrain
     * @param low  minimum limit
     * @param high maximum limit
     */
    fun constrain(n: Float, low: Float, high: Float): Float {
        return max(min(n, high), low)
    }

    /**
     * Re-maps a number from one range to another.
     *
     * @param value  the incoming value to be converted
     * @param start1 lower bound of the value's current range
     * @param stop1  upper bound of the value's current range
     * @param start2 lower bound of the value's target range
     * @param stop2  upper bound of the value's target range
     * @param withinBounds constrain the value to the newly mapped range
     * @return remapped number
     */
    fun map(n: Float, start1: Float, stop1: Float, start2: Float, stop2: Float, withBounds: Boolean = true): Float {
        val newval = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2
        return if (!withBounds) {
            newval
        } else {
            if (start2 < stop2) {
                constrain(newval, start2, stop2)
            } else {
                constrain(newval, stop2, start2)
            }
        }
    }

    /**
     * Calculates a number between two numbers at a specific increment. The amt
     * parameter is the amount to interpolate between the two values where 0.0
     * equal to the first point, 0.1 is very near the first point, 0.5 is
     * half-way in between, and 1.0 is equal to the second point. If the
     * value of amt is more than 1.0 or less than 0.0, the number will be
     * calculated accordingly in the ratio of the two given numbers. The lerp
     * function is convenient for creating motion along a straight
     * path and for drawing dotted lines.
     *
     * @method lerp
     * @param start first value
     * @param stop second value
     * @param amt number
     * @return lerped value
     */
    fun lerp(start: Float, stop: Float, amt: Float): Float {
        return amt * (stop - start) + start
    }

    /**
     * 求最大公约数
     *
     * @return
     */

    fun gcd(m: Int, n: Int): Int {
        var m = m
        var n = n
        if (n == 0) {
            return 0
        }
        var r = m % n
        while (r > 0) {
            m = n
            n = r
            r = m % n
        }
        return n
    }

    /**
     * 约分
     *
     * @return
     */
    fun fraction(m: Int, n: Int): Pair<Int, Int> {
        val r = gcd(m, n)
        return if (r == 0) {
            Pair(m, n)
        } else Pair(m / r, n / r)
    }


    /**
     * Returns the maximum value of Y axis
     * @param yMax Maximum value in the Y axis
     * @param yStepSize size of one step in the Y axis
     */
    fun getMaxElement(yMax: Float, yStepSize: Int): Int {
        var reqYLabelsQuo = (yMax / yStepSize)
        val reqYLabelsRem = yMax.rem(yStepSize)
        if (reqYLabelsRem > 0f) {
            reqYLabelsQuo += 1
        }
        return reqYLabelsQuo.toInt() * yStepSize
    }


    /**
     * Normalizes a number from another range into a value between 0 and 1.
     * Identical to map(value, low, high, 0, 1).
     * Numbers outside of the range are not clamped to 0 and 1, because
     * out-of-range values are often intentional and useful. (See the example above.)
     *
     * @method norm
     * @param n incoming value to be normalized
     * @param start lower bound of the value's current range
     * @param stop  upper bound of the value's current range
     */
    fun norm(n: Float, start: Float, stop: Float): Float {
        return map(n, start, stop, 0f, 1f)
    }


    fun mapValueFromRangeToRange(
        value: Float,
        fromLow: Float,
        fromHigh: Float,
        toLow: Float,
        toHigh: Float
    ): Float {
        return toLow + (value - fromLow) / (fromHigh - fromLow) * (toHigh - toLow)
    }


    //两点间的角度
    fun getDegree(sx: Float, sy: Float, tx: Float, ty: Float): Double {
        return getRadian(sx, sy, tx, ty).radiansToDegrees()
    }

    fun getRadian(sx: Float, sy: Float, tx: Float, ty: Float): Double {
        val nX = tx - sx
        val nY = ty - sy
        var angrad = 0.0
        var angel = 0.0
        var tpi = 0.0
        var tan = 0.0f
        if (nX.compareTo(0.0f) != 0) {
            tan = abs(nY / nX)
            angel = atan(tan).toDouble()
            angrad = if (nX.compareTo(0.0f) == 1) {
                (nY.compareTo(0.0f) == 1 || nY.compareTo(0.0f) == 0).ifValue(angel, 2 * PI - angel)
            } else {
                (nY.compareTo(0.0f) == 1 || nY.compareTo(0.0f) == 0).ifValue(PI - angel, PI + angel)
            }
        } else {
            tpi = PI / 2
            angrad =  (nY.compareTo(0.0f) == 1).ifValue(tpi,-1 * tpi)
        }
        return angrad
    }



    fun calcX(cx: Float, radius: Float, angle: Float): Float {
        return (cx + radius * cos(angle.degreesToRadians()))
    }

    fun calcY(cy: Float, radius: Float, angle: Float): Float {
        return (cy + radius * sin(angle.degreesToRadians()))
    }

    /**
     * Calculate the [BitmapFactory.Options.inSampleSize] given the source dimensions of the image
     * ([srcWidth] and [srcHeight]), the output dimensions ([dstWidth], [dstHeight]), and the [scaleStyle].
     */

    fun calculateInSampleSize(
        srcWidth: Int,
        srcHeight: Int,
        dstWidth: Int,
        dstHeight: Int,
        scaleStyle: ScaleStyle
    ): Int {
        val widthInSampleSize = (srcWidth / dstWidth).takeHighestOneBit()
        val heightInSampleSize = (srcHeight / dstHeight).takeHighestOneBit()
        return when (scaleStyle) {
            ScaleStyle.FILL -> minOf(widthInSampleSize, heightInSampleSize)
            ScaleStyle.FIT -> maxOf(widthInSampleSize, heightInSampleSize)
        }.coerceAtLeast(1)
    }

    /**
     * Calculate the percentage to multiply the source dimensions by to fit/fill the destination
     * dimensions while preserving aspect ratio.
     */

    fun computeSizeMultiplier(
        srcWidth: Int,
        srcHeight: Int,
        dstWidth: Int,
        dstHeight: Int,
        scaleStyle: ScaleStyle
    ): Double {
        val widthPercent = dstWidth / srcWidth.toDouble()
        val heightPercent = dstHeight / srcHeight.toDouble()
        return when (scaleStyle) {
            ScaleStyle.FILL -> maxOf(widthPercent, heightPercent)
            ScaleStyle.FIT -> minOf(widthPercent, heightPercent)
        }
    }

    /** @see computeSizeMultiplier */

    fun computeSizeMultiplier(
        srcWidth: Float,
        srcHeight: Float,
        dstWidth: Float,
        dstHeight: Float,
        scaleStyle: ScaleStyle
    ): Float {
        val widthPercent = dstWidth / srcWidth
        val heightPercent = dstHeight / srcHeight
        return when (scaleStyle) {
            ScaleStyle.FILL -> maxOf(widthPercent, heightPercent)
            ScaleStyle.FIT -> minOf(widthPercent, heightPercent)
        }
    }

    /** @see computeSizeMultiplier */

    fun computeSizeMultiplier(
        srcWidth: Double,
        srcHeight: Double,
        dstWidth: Double,
        dstHeight: Double,
        scaleStyle: ScaleStyle
    ): Double {
        val widthPercent = dstWidth / srcWidth
        val heightPercent = dstHeight / srcHeight
        return when (scaleStyle) {
            ScaleStyle.FILL -> maxOf(widthPercent, heightPercent)
            ScaleStyle.FIT -> minOf(widthPercent, heightPercent)
        }
    }

    fun decimalLength(v: Long): Int {
        if (v >= 1000000000000000000L) return 19
        if (v >= 100000000000000000L) return 18
        if (v >= 10000000000000000L) return 17
        if (v >= 1000000000000000L) return 16
        if (v >= 100000000000000L) return 15
        if (v >= 10000000000000L) return 14
        if (v >= 1000000000000L) return 13
        if (v >= 100000000000L) return 12
        if (v >= 10000000000L) return 11
        if (v >= 1000000000L) return 10
        if (v >= 100000000L) return 9
        if (v >= 10000000L) return 8
        if (v >= 1000000L) return 7
        if (v >= 100000L) return 6
        if (v >= 10000L) return 5
        if (v >= 1000L) return 4
        if (v >= 100L) return 3
        return if (v >= 10L) 2 else 1
    }

    /**
     * 获取居中的值
     */
     fun midOf(min: Int, number: Int, max: Int): Int = maxOf(min, minOf(number, max))
     fun midOf(min: Float, number: Float, max: Float): Float = maxOf(min, minOf(number, max))


    /**
     * 获取正态分布的数据
     */
    fun getDistributedArray(n: Int, times: Int, r: Random): IntArray {
        val avg = n / 2
        val v: Int = if (n <= 50) {
            n
        } else if (n <= 100) {
            (n * 1.5).toInt()
        } else if (n <= 200) {
            n * 2
        } else {
            n * 3
        }
        val count = n * times
        val a = IntArray(count)
        var i = 0
        while (i < count) {
            val x = (sqrt(v.toDouble()) * r.nextGaussian() + avg) as Int
            if (x in 0 until n) {
                a[i++] = x
            }
        }
        return a
    }


    /**
     * Will result in a graph like:
     *
     *  \     /
     *   \___/
     *
     * Where the length of ___ = 2 x halfCenterRange
     */
    fun cutOffCenter(ownValue: Float, movingValue: Float, halfCenterRange: Float) =
        (abs(ownValue - movingValue) - halfCenterRange).coerceAtLeast(0f)

    /**
     * Will result in a graph like:
     *
     *        /
     *    ___/
     *  /
     *
     * Where the length of ___ = 2 x halfCenterRange
     */
    fun cutOffCenterSigned(ownValue: Float, movingValue: Float, halfCenterRange: Float) =
        cutOffCenter(ownValue, movingValue, halfCenterRange) * sign(ownValue - movingValue)

    fun scaleUpTo(v: Float, scale: Float, slope: Float) =
        (slope * scale * v).coerceIn(-abs(scale), abs(scale))


    /**
     * Returns the value of x constrained to the range minVal to maxVal. The returned value is computed
     * x.coerceIn(min, max).
     *
     * @param x Specify the value to constrain.
     * @param minVal Specify the lower end of the range into which to constrain x.
     * @param maxVal Specify the upper end of the range into which to constrain x.
     *
     * Based on the OpenGL Shading Language clamp function.
     * https://registry.khronos.org/OpenGL-Refpages/gl4/html/clamp.xhtml
     */
    fun clamp(x: Float, min: Float, max: Float): Float =
        x.coerceIn(min, max)

    /**
     * Performs smooth Hermite interpolation between 0 and 1 when edge0 < x < edge1. This is useful in
     * cases where a threshold function with a smooth transition is desired.
     *
     * @param edge0 Specifies the value of the lower edge of the Hermite function.
     * @param edge1 Specifies the value of the upper edge of the Hermite function.
     * @param x Specifies the source value for interpolation.
     *
     * Based on the OpenGL Shading Language smoothstep function.
     * https://registry.khronos.org/OpenGL-Refpages/gl4/html/smoothstep.xhtml
     */
    @Suppress("MagicNumber")
    fun smoothstep(edge0: Float, edge1: Float, x: Float): Float =
        clamp((x - edge0) / (edge1 - edge0), 0f, 1f).run {
            this * this * (3f - 2f * this)
        }

    fun splitToRange(size: Long, minPartSize: Long, maxPartCount: Long): List<LongRange> {
        require(size >= 1) {
            "size must be >=1 passed :$size"
        }
        require(minPartSize >= 1) {
            "minPartSize must be >=1 passed :$minPartSize"
        }
        require(maxPartCount >= 1) {
            "maxPartCount must be >=1 passed :$maxPartCount"
        }

        val minParts = (size + minPartSize - 1) / minPartSize // round up division
        val actualPartCount = minOf(maxPartCount, minParts)
        val idealPartSize = size / actualPartCount
        val ranges = mutableListOf<LongRange>()
        var start = 0L
        var end = 0L
        for (i in 1..actualPartCount) {
            end = start + idealPartSize - 1
            if (i <= size % actualPartCount) {
                end++
            }
            ranges.add(start..end)
            start = end + 1
        }
        return ranges
    }

    fun sqr(a:Double) = a*a

    fun sqr(a:Float)  = a*a

    fun sqr(a:Int) = a*a

    fun sqr(a:Long) = a*a

    fun distSqr(ax: Double, ay: Double, bx: Double, by: Double) =
        (ax - bx) * (ax - bx) + (ay - by) * (ay - by)

    fun distSqr(ax: Float, ay: Float, bx: Float, by: Float) =
        (ax - bx) * (ax - bx) + (ay - by) * (ay - by)

    fun distSqr(ax: Int, ay: Int, bx: Int, by: Int) =
        distSqr(ax.toDouble(), ay.toDouble(), bx.toDouble(), by.toDouble())

    fun dist(ax: Double, ay: Double, bx: Double, by: Double) = sqrt(distSqr(ax, ay, bx, by))

    fun dist(ax: Float, ay: Float, bx: Float, by: Float) = sqrt(distSqr(ax, ay, bx, by))

    fun dist(ax: Int, ay: Int, bx: Int, by: Int) =
        dist(ax.toDouble(), ay.toDouble(), bx.toDouble(), by.toDouble())

    /**
     * The Gamma class contains functions related to the gamma function.
     *
     * @see [Gamma Function](https://en.wikipedia.org/wiki/Gamma_function)
     * @since 1.0.0
     * @author Matthias Kovacic
     */
    object Gamma {

        /**
         * Returns the gamma function of a real number using the Lanczos approximation.
         *
         * @param a The real number to find the gamma function of.
         * @return The gamma function of the real number.
         */
        fun gamma(a: Double): Double {
            var z = a
            val g = 7.0
            val p = arrayOf(
                0.9999999999998099, 676.5203681218851, -1259.1392167224028,
                771.3234287776531, -176.6150291621406, 12.507343278686905,
                -0.13857109526572012, 9.984369578019572E-6, 1.5056327351493116e-7
            )

            return if (z < 0.5) {
                PI / (sin(PI * z) * gamma(1.0 - z)) // Reflection formula
            } else {
                z -= 1.0
                var x = p[0]
                for (i in 1 until p.size)
                    x += p[i] / (z + i)
                val t = z + g + 0.5
                sqrt(2 * PI) * t.pow(z + 0.5) * exp(-t) * x
            }
        }

        /**
         * Returns the incomplete gamma function of a real number.
         *
         * @param a The shape parameter.
         * @param x The real number to find the incomplete gamma function of.
         * @return The incomplete gamma function of the real number.
         */
        fun incompleteGamma(a: Double, x: Double): Double {
            val gammaApprox = gamma(a)
            var term = exp(-x) * x.pow(a) / a
            var sum = term
            var k = 1
            while (term / sum > 1e-15) {
                term *= x / (a + k)
                sum += term
                k++
            }

            return gammaApprox * sum
        }

        /**
         * Returns the digamma function of a real number.
         *
         * @param x The real number to find the digamma function of.
         * @return The digamma function of the real number.
         */
        fun diGamma(x: Double): Double {
            var result = -EULER_MASCHERONI - 1 / x
            var n = 1
            var term = 1 / (x * x)
            while (term / result > EPSILON) {
                result += term
                term = n * (n + 1) / (n + x) / (n + x)
                n++
            }
            return result
        }

        /**
         * Return the log gamma function of a real number using Stirling's approximation.
         *
         * @param x The real number to find the log gamma function of.
         * @return The log gamma function of the real number.
         */
        fun lnGamma(x: Double): Double {
            val c = arrayOf(
                1.000000000190015, 76.18009172947146, -86.50532032941678,
                24.01409824083091, -1.231739572450155, 0.1208650973866179e-2, -0.5395239384953e-5
            )

            var y = x
            var tmp = x + 5.5
            tmp -= (x + 0.5) * ln(tmp)
            var ser = 1.000000000190015
            for (element in c) {
                y++
                ser += element / y
            }
            return -tmp + ln(SQRT_TAU * ser / x)
        }

    }

    /**
     * Integer version of log2(x)
     *
     * from http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
     */
    fun log2(x: Int): Int {
        var x = x
        var r = 0 // result of log2(v) will go here
        if (x and 0xFFFF0000.toInt() != 0) {
            x = x shr 16
            r = r or 16
        }
        if (x and 0xFF00 != 0) {
            x = x shr 8
            r = r or 8
        }
        if (x and 0xF0 != 0) {
            x = x shr 4
            r = r or 4
        }
        if (x and 0xC != 0) {
            x = x shr 2
            r = r or 2
        }
        if (x and 0x2 != 0) {
            r = r or 1
        }
        return r
    }

}


/**
 * Constant by which to multiply an angular value in degrees to obtain an
 * angular value in radians.
 */
const val DEGREES_TO_RADIANS = 0.017453292519943295

/**
 * Constant by which to multiply an angular value in radians to obtain an
 * angular value in degrees.
 */
const val RADIANS_TO_DEGREES = 57.29577951308232

fun Math.toRadians(value: Double) = value.degreesToRadians()
fun Math.toDegrees(value: Double) = value.radiansToDegrees()

fun Math.toRadians(value: Float) = value.degreesToRadians()
fun Math.toDegrees(value: Float) = value.radiansToDegrees()

fun Math.toRadians(value: Int) = value.degreesToRadians()
fun Math.toDegrees(value: Int) = value.radiansToDegrees()

fun Math.toRadians(value: Long) = value.degreesToRadians()

fun Math.toDegrees(value: Long) = value.radiansToDegrees()

/**
 * Converts degree to radian.
 */
fun Float.degreesToRadians():Float = (this * DEGREES_TO_RADIANS).toFloat()

fun Float.radiansToDegrees():Float = (this * RADIANS_TO_DEGREES).toFloat()

fun Double.degreesToRadians() = this * DEGREES_TO_RADIANS

fun Double.radiansToDegrees() = this * RADIANS_TO_DEGREES

fun Int.degreesToRadians() = this * DEGREES_TO_RADIANS

fun Int.radiansToDegrees() = this * RADIANS_TO_DEGREES

fun Long.degreesToRadians() = this * DEGREES_TO_RADIANS

fun Long.radiansToDegrees() = this * RADIANS_TO_DEGREES

